/*
 * Copyright (c) 1989 - 1999 Microsoft Corporation
 */

#include "precomp.h"
#pragma hdrstop

#include "mrxglobs.h"
#include "host_access.h"

//
//  forwards & pragmas
//

NTSTATUS
HwFsCreateFileSuccessTail(
    PRX_CONTEXT     RxContext,
    RX_FILE_TYPE    StorageType,
    ULONG           CreateAction,
    PHWFS_FILE_INFO HwFsFileInfo
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, HwFsCreate)
#pragma alloc_text(PAGE, HwFsShouldTryToCollapseThisOpen)
#pragma alloc_text(PAGE, HwFsCreateFileSuccessTail)
#endif

NTSTATUS
HwFsShouldTryToCollapseThisOpen(
    IN PRX_CONTEXT RxContext)
{
    // This routine determines if the mini knows of a good reason
    // not to try collapsing on this open.
    // We detect that share should be collapsed if ShareId changed.
    //
    // return STATUS_SUCCESS = ok to try to collapse
    //        MORE_PROCESSING_REQUIRED = don't collapse
    NTSTATUS Status = STATUS_SUCCESS;
    PMRX_SRV_OPEN            pSrvOpen;
    PHWFS_SRVOPEN_EXTENSION  pSrvOpenExt;
    PMRX_FCB                 pFcb;
    PHWFS_NETROOT_EXTENSION  pNetRootExt;

    PAGED_CODE();

    if (!RxContext->pRelevantSrvOpen || !RxContext->pFcb) {
        return STATUS_SUCCESS;
    }

    pFcb = RxContext->pFcb;
    pSrvOpen = RxContext->pRelevantSrvOpen;
    if (pSrvOpen->Context || !pFcb->pNetRoot || !pFcb->pNetRoot->Context) {
        return STATUS_SUCCESS;
    }

    pSrvOpenExt = pSrvOpen->Context;
    pNetRootExt = pFcb->pNetRoot->Context;
    if (pNetRootExt->Share.ShareGeneration != pSrvOpenExt->HwfsFile.ShareGeneration) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("Collapsing SrvOpen for %x:%x\n",
            pSrvOpenExt->HwfsFile.ShareId, pSrvOpenExt->HwfsFile.FuseNodeId));

        return STATUS_MORE_PROCESSING_REQUIRED;
    }

    return STATUS_SUCCESS;
}

NTSTATUS
HwFsCreate(
    IN OUT PRX_CONTEXT RxContext
    )
/*
 * Open/Create File/Directory
 */
{
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG CreateAction = FILE_CREATED;
    HWFS_FILE_INFO HwFsFileInfo;

    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    PHWFS_FCB_EXTENSION pFcbExtension = HWFS_GET_FCB_EXTENSION(Fcb);
    PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    HWFS_SRVOPEN_EXTENSION* pSrvOpenExt = SrvOpen->Context;
    PMRX_SRV_CALL SrvCall = RxContext->Create.pSrvCall;
    PHWFS_DEVICE_EXTENSION   pDevExt = HWFS_GET_DEVICE_EXTENSION(RxContext);
    __notnull PMRX_NET_ROOT NetRoot = Fcb->pNetRoot;
    UNICODE_STRING RemainingName = *SrvOpen->pAlreadyPrefixedName;
    NT_CREATE_PARAMETERS *pCreateParams = &RxContext->Create.NtCreateParameters;
    ACCESS_MASK DesiredAccess = pCreateParams->DesiredAccess;
    PHWFS_NETROOT_EXTENSION pNetRootExt = HWFS_GET_NETROOT_EXTENSION(NetRoot);

    RxTraceEnter("HwFsCreate");
    PAGED_CODE();

    RxDefineNode(pSrvOpenExt, HWFS_SRVOPEN_EXTENSION);

    HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN, ("Attempt to open '%wZ' Len is % d\n", &RemainingName, RemainingName.Length));
    // strip leading '\\'
    if (RemainingName.Length > 0 && RemainingName.Buffer[0] == L'\\') {
        RemainingName.Buffer++;
        RemainingName.Length -= sizeof(WCHAR);
        RemainingName.MaximumLength -= sizeof(WCHAR);
    }

    if (NetRoot->Type != NET_ROOT_DISK) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("HwFsCreate: Unsupported NetRootType %u\n", (unsigned)NetRoot->Type));
        Status = STATUS_NOT_IMPLEMENTED;
        goto Exit;
    }

    if (pCreateParams->CreateOptions & FILE_OPEN_BY_FILE_ID) {
        HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN, ("HwFsCreate: Unsupported FILE_OPEN_BY_FILE_ID\n"));
        Status = STATUS_NOT_IMPLEMENTED;
        goto Exit;
    }

    if (Fcb->FcbState & FCB_STATE_PAGING_FILE) {
        HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN, ("HwFsCreate: PAGE_FILE is not supported\n"));
        Status = STATUS_NOT_IMPLEMENTED;
        goto Exit;
    }

    while (TRUE) {
        //
        // Here we have an untrivial synchronization issue.
        // If HostCreateFile returns STATUS_INVALID_HANDLE,
        // this means that share was disconnected at host side.
        //
        // But it could happen that share was recreated immediately.
        // So, we have to try to reconnect. Otherwise, it will be a wierd
        // behaviour for the client: share exists, but CreateFile() returned error.
        //
        // The problem is that ConnectShare() can be done in parallels in another
        // thread. So, we have to guard Share and check that other thread
        // have not reconnected it yet.
        //
        // To overcome this, we remember the ShareGeneration. And pass it
        // to HostConnectShare(). HostConnectShare() under lock will check that ShareGeneration
        // have not changed. If it does, then another thread already reconnected it.
        UINT64 ShareGeneration = pNetRootExt->Share.ShareGeneration;

        Status = HostCreateFile(
            pDevExt,
            &pNetRootExt->Share,
            &RemainingName,
            pCreateParams,
            &pSrvOpenExt->HwfsFile,
            &CreateAction,
            &HwFsFileInfo);
        if (Status == STATUS_SUCCESS) {
            break;
        }

        if (Status != STATUS_INVALID_HANDLE) {
            HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN, ("HostCreateFile() failed: 0x%08x\n", Status));
            goto Exit;
        }

        //
        // Share doesn't exist. Try reconnect and retry open
        //
        BOOLEAN Reconnected = HostReconnectShare(pDevExt, &pNetRootExt->Share, ShareGeneration);
        HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("HostCreateFile() share reconnected: %d\n", Reconnected));
        if (!Reconnected) {
            Status = STATUS_OBJECT_PATH_NOT_FOUND;
            goto Exit;
        }
    }

    pSrvOpenExt->IsReadAccess = pCreateParams->DesiredAccess & (FILE_EXECUTE | FILE_READ_ACCESS);
    pSrvOpenExt->IsWriteAccess = pCreateParams->DesiredAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA);

    Status = HwFsCreateFileSuccessTail(
        RxContext,
        pSrvOpenExt->HwfsFile.IsDir ? FileTypeDirectory : FileTypeFile,
        CreateAction,
        &HwFsFileInfo
    );
    if (Status != STATUS_SUCCESS) {
        //
        //  alloc error..
        //
        HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("Failed to allocate Fobx \n"));
        goto Exit;
    }

    if (!RxIsFcbAcquiredExclusive(Fcb)) {
        ASSERT(!RxIsFcbAcquiredShared(Fcb));
        RxAcquireExclusiveFcbResourceInMRx(Fcb);
    }

    ASSERT(Status != STATUS_PENDING);
    ASSERT(RxIsFcbAcquiredExclusive(Fcb));

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("NetRoot is 0x%x Fcb is 0x%x SrvOpen is 0x%x Fobx is 0x%x\n",
               NetRoot, Fcb, SrvOpen, RxContext->pFobx));

Exit:

    RxTraceLeave(Status);
    return Status;
}

NTSTATUS
HwFsCreateFileSuccessTail(
    PRX_CONTEXT     RxContext,
    RX_FILE_TYPE    StorageType,
    ULONG           CreateAction,
    PHWFS_FILE_INFO HwFsFileInfo
    )
/*++

Routine Description:

    This routine finishes the initialization of the fcb and srvopen for a
successful open.

Return Value:

    RXSTATUS - The return status for the operation

--*/
{
    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    PHWFS_FCB_EXTENSION pFcbExt = Fcb->Context;
    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    HWFS_SRVOPEN_EXTENSION* pSrvOpenExt = SrvOpen->Context;

    FILE_BASIC_INFORMATION *bi = &HwFsFileInfo->BasicInfo;
    FILE_STANDARD_INFORMATION* si = &HwFsFileInfo->StandardInfo;

    FCB_INIT_PACKET InitPacket;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("MRxExCreateFileSuccessTail\n"));
    PAGED_CODE();

    ASSERT(NodeType(SrvOpen) == RDBSS_NTC_SRVOPEN);
    ASSERT(NodeType(RxContext) == RDBSS_NTC_RX_CONTEXT);

    if (RxContext->pFobx == NULL) {
        // This Fobx should be cleaned up by the wrapper
        RxContext->pFobx = RxCreateNetFobx(RxContext, SrvOpen);
        if (!RxContext->pFobx) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    ASSERT(RxIsFcbAcquiredExclusive(Fcb));
    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("Storagetype %08lx/Action %08lx\n", StorageType, CreateAction));

    RxContext->Create.ReturnedCreateInformation = CreateAction;

    BOOLEAN bFirstOpen = (Fcb->OpenCount == 0);
    if (bFirstOpen) {
        // First open of this FCB
        RxFormInitPacket(
            InitPacket,
            &bi->FileAttributes,
            &si->NumberOfLinks,
            &bi->CreationTime,
            &bi->LastAccessTime,
            &bi->LastWriteTime,
            &bi->ChangeTime,
            &si->AllocationSize,
            &si->EndOfFile,
            &si->EndOfFile);

        RxFinishFcbInitialization(Fcb, RDBSS_STORAGE_NTC(StorageType), &InitPacket);

        RxDefineNode(pFcbExt, HWFS_FCB_EXTENSION);
    } else {
        ASSERT( StorageType == 0 || NodeType(Fcb) ==  RDBSS_STORAGE_NTC(StorageType));
    }

    if (pSrvOpenExt->IsWriteAccess) {
        pFcbExt->WriteOpenCnt++;
        if (!pSrvOpenExt->IsReadAccess) {
            pFcbExt->WriteOnlyOpenCnt++;
        }
    }

    pFcbExt->FileInfo = *HwFsFileInfo;

    //
    // Each Open recomputes buffering.
    // Field BufferingFlags is used just as a parameter.
    // We don't need to bother with older opens. Just fill how we want this to be after this open.
    //
    SrvOpen->BufferingFlags |=
        FCB_STATE_WRITECACHING_ENABLED |
        FCB_STATE_WRITEBUFFERING_ENABLED |
        FCB_STATE_READCACHING_ENABLED |
        FCB_STATE_READBUFFERING_ENABLED;
    //
    // We should not use write-cache, if there are more than 1 client opened the file.
    // Otherwise, other clients will see incoherent data.
    //
    if (pFcbExt->WriteOnlyOpenCnt || (!bFirstOpen && pFcbExt->WriteOpenCnt > 0)) {
        HWFS_DBG_PRINT(DBG_TRACE, DBG_RDR_CACHE,
            ("WriteCache Disabled[%s]: OpenCnt: %I64u, WriteCnt: %I64u, WriteOnlyCnt: %I64u\n",
                pSrvOpenExt->HwfsFile.Utf8FileName ? pSrvOpenExt->HwfsFile.Utf8FileName : "",
                Fcb->OpenCount, pFcbExt->WriteOpenCnt, pFcbExt->WriteOnlyOpenCnt));
        SrvOpen->Flags |= SRVOPEN_FLAG_DONTUSE_WRITE_CACHING;
    }

    RxContext->pFobx->OffsetOfNextEaToReturn = 1;

    return STATUS_SUCCESS;
}

NTSTATUS
HwFsCollapseOpen(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS Status;

    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    RxCaptureRequestPacket;

    __notnull PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
    __notnull PMRX_SRV_CALL SrvCall = RxContext->Create.pSrvCall;
    __notnull PMRX_NET_ROOT NetRoot = Fcb->pNetRoot;

    PHWFS_SRVOPEN_EXTENSION pSrvOpenExt = SrvOpen->Context;

    RxTraceEnter("HwFsCollapseOpen");

    HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN, ("HwFsCollapseOpen; HwFsFileHandle = 0x%I64x",
        pSrvOpenExt ? pSrvOpenExt->HwfsFile.FuseHandle : 0));

    RxContext->pFobx = (PMRX_FOBX)RxCreateNetFobx(RxContext, SrvOpen);

    if (RxContext->pFobx != NULL) {
        ASSERT(RxIsFcbAcquiredExclusive(Fcb));
        RxContext->pFobx->OffsetOfNextEaToReturn = 1;
        capReqPacket->IoStatus.Information = FILE_OPENED;
        Status = STATUS_SUCCESS;
    } else {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        DbgBreakPoint();
    }

    RxTraceLeave(Status);
    return Status;
}

NTSTATUS
HwFsComputeNewBufferingState(
   IN OUT PMRX_SRV_OPEN   pMRxSrvOpen,
   IN     PVOID           pMRxContext,
   OUT    PULONG          pNewBufferingState)
/*++

Routine Description:

   This routine maps specific oplock levels into the appropriate RDBSS
   buffering state flags

Arguments:

   pMRxSrvOpen - the MRX SRV_OPEN extension

   pMRxContext - the context passed to RDBSS at Oplock indication time

   pNewBufferingState - the place holder for the new buffering state
--*/
{
    NTSTATUS Status = STATUS_NOT_IMPLEMENTED;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("HwFsComputeNewBufferingState\n"));
    return Status;
}

NTSTATUS
HwFsDeallocateForFcb (
    IN OUT PMRX_FCB pFcb
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    PHWFS_FCB_EXTENSION pFcbExtension = HWFS_GET_FCB_EXTENSION(pFcb);
    PMRX_NET_ROOT pNetRoot = pFcb->pNetRoot;
    PHWFS_NETROOT_EXTENSION pNetRootExtension = HWFS_GET_NETROOT_EXTENSION(pNetRoot);

    RxTraceEnter("HwFsDeallocateForFcb\n");

    RxTraceLeave(Status);
    return Status;
}

NTSTATUS
HwFsCleanupFobx(
    IN PRX_CONTEXT RxContext)
/*++

Routine Description:

   This routine cleansup a file system object...normally a noop. unless it's a pipe in which case
   we do the close at cleanup time and mark the file as being not open.

Arguments:

    pRxContext - the RDBSS context

Return Value:

    RXSTATUS - The return status for the operation

--*/
{
    NTSTATUS Status = STATUS_SUCCESS;
    PUNICODE_STRING RemainingName;
    __notnull PMRX_FCB Fcb = RxContext->pFcb;
    __notnull PMRX_FOBX Fobx = RxContext->pFobx;

    NODE_TYPE_CODE TypeOfOpen = NodeType(Fcb);

    __notnull PMRX_SRV_OPEN SrvOpen = Fobx->pSrvOpen;

    BOOLEAN SearchHandleOpen = FALSE;

    PAGED_CODE();

    ASSERT(NodeType(SrvOpen) == RDBSS_NTC_SRVOPEN);
    ASSERT(NodeTypeIsFcb(Fcb));

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("HwFsCleanupFobx\n"));

    if (FlagOn(Fcb->FcbState,FCB_STATE_ORPHANED)) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN, ("File orphaned\n"));
        return STATUS_SUCCESS;
    }

    if ((Fcb->pNetRoot->Type != NET_ROOT_PIPE) && !SearchHandleOpen) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN, ("File not for closing at cleanup\n"));
        return STATUS_SUCCESS;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("HwFsCleanup  exit with status=%08lx\n", Status));

    return Status;
}

NTSTATUS
HwFsForcedClose(
    IN PMRX_SRV_OPEN pSrvOpen)
{
    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("HwFsForcedClose\n"));
    return STATUS_SUCCESS;
}

NTSTATUS
HwFsCloseSrvOpen(
    IN     PRX_CONTEXT   RxContext)
{
    // Close the file
    NTSTATUS Status = STATUS_SUCCESS;

    PHWFS_NETROOT_EXTENSION pNetRootExt;
    __notnull PMRX_FOBX pFobx = RxContext->pFobx;
    __notnull PMRX_SRV_OPEN pSrvOpen = pFobx->pSrvOpen;
    PHWFS_SRVOPEN_EXTENSION pSrvOpenExt = pSrvOpen->Context;
    PHWFS_FCB_EXTENSION pFcbExtension = RxContext->pFcb->Context;
    PHWFS_DEVICE_EXTENSION  pDevExt = HWFS_GET_DEVICE_EXTENSION(RxContext);

    if (pSrvOpenExt->IsWriteAccess) {
        pFcbExtension->WriteOpenCnt--;
        if (!pSrvOpenExt->IsReadAccess) {
            pFcbExtension->WriteOnlyOpenCnt--;
        }
    }

    pNetRootExt = RxContext->pFcb->pNetRoot->Context;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("HwFsCloseSrvOpen; HwFsFileHandle = 0x%I64x",
        pSrvOpenExt->HwfsFile.FuseHandle));
    Status = HostCloseFile(pDevExt, &pNetRootExt->Share, &pSrvOpenExt->HwfsFile);

    return Status;
}

NTSTATUS
HwFsDeallocateForFobx (
    IN OUT PMRX_FOBX pFobx
    )
{
    PHWFS_FOBX_EXTENSION pFobxExt;
    pFobxExt = pFobx->Context;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("HwFsDeallocateForFobx; pFobx %p, Ext %p, Data %p\n",
        pFobx, pFobxExt, pFobxExt->Data));

    if (pFobxExt->Data) {
        ExFreePoolWithTag(pFobxExt->Data, 'iDwH');
        pFobxExt->Data = NULL;
    }

    if (pFobxExt->ConvertationBuf) {
        ExFreePool(pFobxExt->ConvertationBuf);
        pFobxExt->ConvertationBuf = NULL;
    }

    FobxFreeTemplate(pFobxExt);

    return STATUS_SUCCESS;
}
